#include "machine.h"
#include "cpu.h"
#include "memory.h"
#include "keyboard.h"
#include "monitor.h"
#include "breakpointtable.h"
#include "runningthread.h"
#include "utilities.h"

#include <QFile>
#include <QTextStream>

Machine* Machine::instance = 0;

Machine::Machine(QObject *parent) :
        QObject(parent),
        cpu(CPU::getInstance(this)), memory(Memory::getInstance(this)),
        keyboard(KeyBoard::getInstance(this)), monitor(Monitor::getInstance(this)),
        breakPointTable(BreakPointTable::getInstance(this)),
        running(new RunningThread(this))
{
    constructTrapTable();
}

Machine::~Machine()
{
}

quint32 Machine::loadProgram(const QString &fileName)
{
    quint32 address;
    quint32 pc = 0;
    QFile file(fileName);
    if(file.open(QFile::ReadOnly))
    {
        QTextStream in(&file);
        QString line;
        while(!(line = in.readLine()).isNull())
        {
            if(!line.isEmpty())
            {
                if(line.startsWith("#"))
                {
                    line = in.readLine();
                    bool ok;
                    address = static_cast<quint32>(line.toLongLong(&ok, 10));
                    pc = address;
                } else {
                    for(int i = 0; i < 4; i++)
                    {
                        memory->write(address + i, line.mid(8 * i, 8));
                    }
                    address += 4;
                }
            }
        }
        file.close();
    }
    return pc;
}

void Machine::step_over()
{
    quint32 pc = cpu->getPC();
    running->setStopAddress(pc + 4);
    running->start();
}

void Machine::step_into()
{
    cpu->exec();
}

void Machine::step_out()
{
    bool ok;
    quint32 address = cpu->getRegister(31).toLongLong(&ok, 2);
    running->setStopAddress(address);
    running->start();
}

void Machine::run()
{
    running->setStopAddress(0);
    running->start();
}

void Machine::stop()
{
    running->stop();
}

Machine* Machine::getInstance(QObject *parent)
{
    if(instance == 0)
    {
        instance = new Machine(parent);
    }
    return instance;
}

CPU* Machine::getCPU()
{
    return cpu;
}

Memory* Machine::getMemory()
{
    return memory;
}

void Machine::constructTrapTable()
{
    bool ok;
    quint32 startAddress = QString("1000").toLongLong(&ok, 16) + QString("100").toLongLong(&ok, 16);
    for(int i = 1; i < 256; i++)
    {
        QString value = Utilities::decToBin(startAddress + QString("400").toLongLong(&ok, 16) * (i - 1));
        quint32 address = i * 4;
        for(int j = 0; j < 4; j++)
        {
            memory->write(address + j, value.mid(j * 8, 8));
        }
    }
}
